#include "GeoFun.h"


#define IMAGECLASS GFImg
#define IMAGEFILE <GeoFun/GeoFun.iml>
#include <Draw/iml_source.h>


//When idraw = 1 it will create image at actual size based on radius values
//Else image created to screen window diaplay size
template <class T>
void DrawCanvas::DoPaint0 ( T& sw, int idraw )
{
	double x1, y1;
	int cnt1, cnt2;
	int RunTo ;

	Sizef sz;

	if ( idraw == 1 )
	{
		sz = Sizef ( 2 * Radius1 + 20, 2 * Radius1 + 20 );
	}

	else
	{
		sz = GetSize();
	}

	sw.Clear ( White() );

	x1 = sz.cx / 2 ;
	y1 = sz.cy / 2 ;

// Show color fill. Draw bacground circle with color

	if ( ColorFill )
	{
		if ( ShowGradient )
		{
			sw.Ellipse ( x1, y1, Radius1, Radius1 ).Fill ( x1, y1, fcolorB1, Radius1, fcolorB2 );
		}

		else
		{
			sw.Ellipse ( x1, y1, Radius1, Radius1 ).Fill ( fcolorB1 );
		}
	}

	cnt1 = data.GetCount();

	for ( int i = 0; i < data.GetCount(); i++ )
	{
		Vector<Pointf>& p = data[i].point;
		sw.Move ( x1 + p[0].x, y1 + p[0].y );

		cnt2 = p.GetCount();

		// If animation flag set do incremental display of curve
		// elase show all curve points.

		RunTo = ShowAnimation ? ShowTo : cnt2 ;
		
		if ( RunTo > cnt2 )
			RunTo = cnt2 ;

		for ( int j = 1; j < RunTo; j++ )
			sw.Line ( x1 + p[j].x, y1 + p[j].y );

		sw.Stroke ( data[i].penwidth, data[i].lcolor );

		sw.EvenOdd ( true );

		if ( ColorFill )
		{
			if ( ShowGradient )
			{
				sw.Fill ( x1, y1, data[i].fcolorM1, Radius1, data[i].fcolorM2, GRADIENT_REFLECT );
			}

			else
			{
				sw.Fill ( data[i].fcolorM1 );
			}
		}

		// Show if option animation set

		if ( ShowAnimation )
		{
			sw.BeginOnPath ( 1.0 );
			sw.DrawImage ( 0, -GFImg::Bee().GetHeight() / 2, GFImg::Bee() );
			sw.End();

			sw.Ellipse ( x1, y1, BaseR, BaseR ).Stroke ( 3, Blue() );
			sw.Ellipse ( x1 + data[i].CenR[RunTo-2].x, y1 + data[i].CenR[RunTo-2].y, RollR, RollR ).Stroke ( 3, LtBlue() );

			sw.Move ( x1, y1 );
			sw.Line ( x1 + data[i].CenR[RunTo-2].x, y1 + data[i].CenR[RunTo-2].y );
			sw.Line ( x1 + data[i].point[RunTo-2].x, y1 + data[i].point[RunTo-2].y );
			sw.Stroke ( 3, Red() );
			ColorFill = false ; // this is required to stop colorfill if other options are changed during animation

		}
	}
}

DrawCanvas::DrawCanvas()
{

}

// at present this function is redundant but kept here to improve printing in future
void DrawCanvas::DoPaint ( Painter& sw )
{
	bool painting = false ;

	if ( painting )
	{
		PaintingPainter h ( 4000, 4000 );
		DoPaint0 ( h, 0 );
		sw.Paint ( h );
	}

	else

		DoPaint0 ( sw, 0 );
}


void DrawCanvas::Paint ( Draw& drw )
{
	Size sz = GetSize();
	ImageBuffer ib ( sz );
	BufferPainter sw ( ib );

	bool transparent = false ;

	DoPaint ( sw );
	drw.DrawImage ( 0, 0, ib );
}


Image GeoFun::GetImage()
{
	Size sz = Size ( int ( dc1.Radius1 * 2 + 20 ), int ( dc1.Radius1 * 2 + 20 ) );
	ImageBuffer ib ( sz );
	BufferPainter bp ( ib, MODE_ANTIALIASED );
	dc1.DoPaint0 ( bp, 1 );

	return static_cast<Image>(ib);
}

void GeoFun::SaveToImageFile ( String fileName )
{
	GuiLock __;

	if ( IsNull ( fileName ) )
	{
		FileSel fs;

		fs.Type ( "Файл PNG", "*.png" );
		fs.Type ( "Файл JPEG", "*.jpg" );

		if ( !fs.ExecuteSaveAs ( t_ ( "Сохранение чертежа в файле PNG или JPEG" ) ) )
		{
			Exclamation ( t_ ( "Чертёж не сохранён" ) );
			return;
		}

		fileName = fs;
	}

	if ( GetFileExt ( fileName ) == ".png" )
	{
		PNGEncoder encoder;
		encoder.SaveFile ( fileName, GetImage() );
	}

	else
		if ( GetFileExt ( fileName ) == ".jpg" )
		{
			JPGEncoder encoder ( 90 );
			encoder.SaveFile ( fileName, GetImage() );
		}

		else
			Exclamation ( Format ( t_ ( "Формат файла \"%s\" не найден" ), GetFileExt ( fileName ) ) );
}

void GeoFun::SaveToAutoCadScript ( String fileName )
{
	if ( IsNull ( fileName ) )
	{
		FileSel fs;

		fs.Type ( "SCR file", "*.scr" );

		if ( !fs.ExecuteSaveAs ( t_ ( "Сохранеие чертежа в Polyline -- AutoCAD Script " ) ) )
		{
			Exclamation ( t_ ( "Чертёж не сохранён" ) );
			return;
		}

		fileName = fs;
	}

	if ( GetFileExt ( fileName ) == ".scr" )
	{

		String sScr ;

		for ( int i = 0; i < dc1.data.GetCount(); i++ )
		{
			sScr << "PLINE\n" ;
			Vector<Pointf>& p = dc1.data[i].point;

			for ( int j = 0; j < p.GetCount(); j++ )
				sScr << p[j].x << "," << p[j].y << "\n";

			sScr << "\nZ\nE\n";
		}

		FileOut fo ;

		fo.Open ( fileName );
		fo.Put ( sScr );
		fo.Close();
	}
}


void GeoFun::SaveToSVG ( String fileName )
{
	if ( IsNull ( fileName ) )
	{
		FileSel fs;

		fs.Type ( "SVG file", "*.svg" );

		if ( !fs.ExecuteSaveAs ( t_ ( "Сохранение файла SVG " ) ) )
		{
			Exclamation ( t_ ( "SVG не сохранён" ) );
			return;
		}

		fileName = fs;
	}

	if ( GetFileExt ( fileName ) == ".svg" )
	{
		String sXML ;

		sXML.Clear();

		sXML << "<?xml version=\"1.0\" standalone=\"no\"?>" ;
		sXML <<  "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" " ;
		sXML <<  "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">" ;
		sXML <<  "<svg width=\"100%\" height=\"100%\" version=\"1.1\" " ;
		sXML <<  "xmlns=\"http://www.w3.org/2000/svg\"> \n  \n" ;

		for ( int i = 0; i < dc1.data.GetCount(); i++ )
		{
			sXML << "\n<polygon points=\" ";
			Vector<Pointf>& p = dc1.data[i].point;

			for ( int j = 0; j < p.GetCount(); j++ )
				sXML << Format ( "%0.4f", p[j].x ) << "," << Format ( "%0.4f",  p[j].y ) << " ";

			sXML << "\" style=\"fill:none;stroke:#00C000;stroke-width:0.4\" />\n" ;
		}

		sXML << "</svg>\n";

		FileOut fo ;
		fo.Open ( fileName );
		fo.Put ( sXML );
		fo.Close();
	}
}

void GeoFun::Print()
{
	Report r;
	String sQtf;

	r.SetPageSize ( 4400, 4400 );

	Size sz = Size ( 4200, 4200 );
	Image im = GetImage();

	qtfim = CreatePNGObject ( im, Size ( 600, 600 ), sz );

	sQtf << "[s0;= " ;
	sQtf << qtfim << " ]";

	r << sQtf ;

	Perform ( r );
}


// This function calculates curve data points for display
void GeoFun::PrepareData()
{
	int p, q, pw;

	double a, b, h ;
	double x, y;
	double Period, step, t ;

	step = 1.0 ; // value in deg

	int q1 = InputPane.edValP ;

	if ( q1 > 0 )
	{
		p = InputPane.edValP ;
	}

	else
	{
		p = 1 ;
		InputPane.edValP.SetData ( p ) ;
	}

	q1 = InputPane.edValQ ;

	if ( q1 > 0 )
	{
		q = InputPane.edValQ ;
	}

	else
	{
		q = 1 ;
		InputPane.edValQ.SetData ( q ) ;
	}

	q1 = InputPane.edPenWidth ;

	if ( q1 > 0 )
	{
		pw = InputPane.edPenWidth ;
	}

	else
	{
		pw = 1 ;
		InputPane.edPenWidth.SetData ( pw ) ;
	}

	Period = 2 * M_PI * p ;

	int iCrv = ~InputPane.CurveType;

	if ( ( iCrv == 1 ) || ( iCrv == 3 ) )
	{
		p = -p ;
	}

	double tmp = InputPane.edBaseCircleRad;

	if ( tmp < 10 )
	{
		a = 10 ;
		InputPane.edBaseCircleRad.SetData ( a );
	}

	else
	{
		a = InputPane.edBaseCircleRad;
	}

	b = ( double ) p * a / ( double ) q;

	dc1.BaseR = a ;
	dc1.RollR = fabs ( b );

	if ( iCrv < 2 )
	{
		h = fabs ( b );
	}

	else
	{
		tmp = InputPane.edArmLength ;

		if ( tmp < 0 )
		{
			h = 0 ;
			InputPane.edArmLength.SetData ( h ) ;
		}

		else
		{
			h = InputPane.edArmLength ;
		}
	}

	dc1.Radius1 = fabs ( a + b ) + h ;

	InputPane.RollingRad = "Радиус Вращения : " + Format ( "%0.2f", b ) ;

	dc1.Zoomed = InputPane.optZoom.GetData();

	if ( dc1.Zoomed )
	{
		Size sz = dc1.GetSize();
		double dmax = sz.cx ;

		if ( sz.cy < dmax )
			dmax = sz.cy;

		dmax -= 10 ;

		double zScale = fabs ( dmax / ( 2 * dc1.Radius1 ) );

		a = a * zScale;

		h = h * zScale;

		b = b * zScale;

		dc1.BaseR = a ;

		dc1.RollR = fabs ( b );

		dc1.Radius1 = fabs ( a + b ) + h ;
	}

	dc1.data.Clear();

	dc1.data.Add();
	dc1.data[0].point.Clear();
	dc1.data[0].CenR.Clear();
	dc1.data[0].lcolor = ~InputPane.LineColor;
	dc1.data[0].fcolorM1 = ~InputPane.FillColorM1;
	dc1.data[0].fcolorM2 = ~InputPane.FillColorM2;
	dc1.data[0].penwidth = pw;

	dc1.fcolorB1 = ~InputPane.FillColorB1;
	dc1.fcolorB2 = ~InputPane.FillColorB2;

	dc1.ShowGradient = ~InputPane.optGradient;
	dc1.ColorFill = ~InputPane.optColorFill;

	int NumSteps = abs ( ( int ) ( 2.0 * ( double ) p * 180.0 / step ) ) + 1 ;

	for ( int i = 0; i < NumSteps; i++ )
	{
		t = ( double ) i / ( double ) NumSteps * Period ;
		x = ( a + b ) * cos ( t ) + h * cos ( ( ( a + b ) / b ) * t ) ;
		y = ( a + b ) * sin ( t ) + h * sin ( ( ( a + b ) / b ) * t );


		Pointf p1;
		p1.Clear();
		p1.x = x ;
		p1.y = y ;
		dc1.data[0].point.Add ( p1 );

		p1.Clear();
		x = ( a + b ) * cos ( t );
		y = ( a + b ) * sin ( t );
		p1.x = x ;
		p1.y = y ;
		dc1.data[0].CenR.Add ( p1 );

	}

	Pointf p1;

	p1.x = dc1.data[0].point[0].x ;
	p1.y = dc1.data[0].point[0].y ;
	dc1.data[0].point.Add ( p1 );

	dc1.Refresh();
}


void GeoFun::ShowAnimated()
{
	int MaxCnt = dc1.data[0].point.GetCount();

	if ( dc1.ShowAnimation )
	{
		if ( dc1.ShowTo < MaxCnt )
		{
			dc1.Refresh();
			dc1.ShowTo += dc1.AnimSpeed ;
		}

		else
		{
			dc1.ShowAnimation = false ;
		}
	}

	else
	{
		Animate.Kill();
		PrepareData();
		dc1.ShowAnimation = false ;
	}
}

void GeoFun::SetAnimation()
{
	dc1.AnimSpeed = InputPane.edAnimSpeed.GetData();
	dc1.ShowTo = 5 ;

	dc1.ShowAnimation = dc1.ShowAnimation ? false : true ;
	dc1.ColorFill = false ;

	if ( dc1.ShowAnimation )
	{
		InputPane.btnShowAnimation.SetLabel ( "Остановить Анимацию" );
	}

	else
	{
		InputPane.btnShowAnimation.SetLabel ( "Начать Анимацию" );
	}

	Animate.KillSet ( -50, [=, this]{ ShowAnimated();} );
}

void GeoFun::FirstDraw()
{
	First = false ;
	PrepareData();

	DUMP ( "First Draw" );
}


void GeoFun::Serialize ( Stream& s )
{
	s
	% InputPane.edArmLength
	% InputPane.edBaseCircleRad
	% InputPane.edPenWidth

	% InputPane.edValP
	% InputPane.edValQ

	% InputPane.LineColor

	% InputPane.FillColorM1
	% InputPane.FillColorM2
	% InputPane.FillColorB1
	% InputPane.FillColorB2

	% InputPane.optGradient
	% InputPane.optColorFill
	% InputPane.CurveType
	% InputPane.optZoom

	% InputPane.edAnimSpeed
	;

	SerializePlacement ( s );
}

// Using layout to redraw on windows resize
void GeoFun::Layout()
{
	if ( !First )
	{
		PrepareData();
	}

	TopWindow::Layout();
}

void GeoFun::About()
{
	WithAboutLayout<TopWindow> dlg;
	dlg.AboutText = GetTopic ( "topic://GeoFun/app/About$ru-ru" ).text;
	CtrlLayoutOK ( dlg, "О Программе" );
	dlg.CenterScreen();
	dlg.Run();
}


void GeoFun::Help()
{
	HelpWindow helpme;
	helpme.GoTo ( "topic://GeoFun/app/Help$ru-ru" );
	helpme.Execute();
}


void GeoFun::Exit()
{
	Close();
}

void GeoFun::SetAnimationSpeed()
{
	dc1.AnimSpeed = InputPane.edAnimSpeed.GetData();

	if ( dc1.AnimSpeed < 1 )
		dc1.AnimSpeed = 1;

	if ( dc1.AnimSpeed > 20 )
		dc1.AnimSpeed = 20;

	InputPane.edAnimSpeed.SetData ( dc1.AnimSpeed );
}


void GeoFun::OptionColorFill()
{
	int i = InputPane.optColorFill.Get();
	i = i ? 0 : 1;
	InputPane.optColorFill.Set ( i )	;
	PrepareData();
}

void GeoFun::OptionGradient()
{
	int i = InputPane.optGradient.Get();
	i = i ? 0 : 1;
	InputPane.optGradient.Set ( i )	;
	PrepareData();
}

void GeoFun::OptionZoom()
{
	int i = InputPane.optZoom.Get();
	i = i ? 0 : 1;
	InputPane.optZoom.Set ( i )	;
	PrepareData();
}

void GeoFun::SetCurveType ( int CrvType )
{
	InputPane.CurveType.SetData ( CrvType );
	SetCurveTypeMenu ( CrvType );
}


void GeoFun::SetCurveTypeMenu ( int CrvType )
{
	EpiCyclo = HypoCyclo = EpiTroch = HypoTroch = false ;

	switch ( CrvType )
	{

		case 1 :
			HypoCyclo = true ;
			break ;

		case 2 :
			EpiTroch = true ;
			break ;

		case 3 :
			HypoTroch = true ;
			break ;

		default :
			EpiCyclo = true ;
	}

	PrepareData();
}


// Menu Functions

void GeoFun::SettingsMenu ( Bar& bar )
{
	bar.Add ( t_ ( "Цветная Заливка" ), [=, this] { OptionColorFill();} ).Check ( dc1.ColorFill )
	.Help ( t_ ( "Изменить настройки" ) );
	bar.Add ( t_ ( "Градиентная Заливка" ), [=, this] { OptionGradient();} ).Check ( dc1.ShowGradient )
	.Help ( t_ ( "Изменить настройки" ) );
	bar.Add ( t_ ( "Растянуть" ), [=, this] { OptionZoom();} ).Check ( dc1.Zoomed )
	.Help ( t_ ( "Растянуть по размеру окна" ) );
}

void GeoFun::FileMenu ( Bar& bar )
{
	bar.Add ( t_ ( "Сохранить рисунок" ), CtrlImg::save_as(), [=, this] {SaveToImageFile("");} )
	.Help ( t_ ( "Сохранить как PNG или JPG" ) );
	bar.Add ( t_ ( "Save AutoCAD Script" ), CtrlImg::save(), [=, this] { SaveToAutoCadScript("");} )
	.Help ( t_ ( "Сохранить как файл AutoCAD Script (.scr)" ) );
	bar.Add ( t_ ( "Сохранить как SVG" ), CtrlImg::save(), [=, this]{ SaveToSVG("");} )
	.Help ( t_ ( "Сохранить как файл Scalable vector graphics (.svg)" ) );
	bar.Separator();
	bar.Add ( t_ ( "Печать" ), CtrlImg::print() , [=, this] { Print();} )
	.Help ( t_ ( "Печать рисунка, страница A4" ) );
	bar.Separator();
	bar.Add ( t_ ( "Выход" ), [=, this] { Exit(); })
	.Help ( t_ ( "Покинуть приложение" ) );
}

void GeoFun::CurvesMenu ( Bar& bar )
{
	bar.Add ( t_ ( "Эпициклоид" ), [=, this] { SetCurveType(0);} )
	.Help ( t_ ( "Начертить кривую Эпициклоид" ) ).Check ( EpiCyclo );
	bar.Add ( t_ ( "Гипоциклоид" ), [=, this] {  SetCurveType( 1 );} )
	.Help ( t_ ( "Начертить кривую Гипоциклоид" ) ).Check ( HypoCyclo );
	bar.Add ( t_ ( "Эпитрохоид" ), [=, this] {  SetCurveType( 2 );} )
	.Help ( t_ ( "Начертить кривую Эпитрохоид" ) ).Check ( EpiTroch );
	bar.Add ( t_ ( "Гипотрохоид" ), [=, this] {  SetCurveType( 3 );} )
	.Help ( t_ ( "Начертить кривую Гипотрохоид" ) ).Check ( HypoTroch );
	bar.Separator();
	bar.Add ( t_ ( "Анимированный Чертёж" ), [=, this] {  SetAnimation( );} )
	.Help ( t_ ( "Показать анимацию черчения кривой" ) ).Check ( AnimMn );
}

void GeoFun::MainMenu ( Bar& bar )
{
	bar.Sub ( t_ ( "Файл" ), [=, this] (Bar& bar) { FileMenu(bar);} );
	bar.Sub ( t_ ( "Кривые" ), [=, this] (Bar& bar) { CurvesMenu(bar);} );
	bar.Sub ( t_ ( "Опции" ), [=, this] (Bar& bar) { SettingsMenu(bar);} );
	bar.Sub ( t_("Ассистент"), [=, this] (Bar& bar) {HelpMenu(bar);} );
}

void GeoFun::HelpMenu ( Bar& bar )
{
	bar.Add ( t_ ( "О Программе" ), CtrlImg::information(), [=, this] { About();} )
	.Help ( t_ ( "О GeoFun" ) );
	bar.Separator();
	bar.Add ( t_ ( "Справка" ), CtrlImg::help(), [=, this] { Help();} )
	.Help ( t_ ( "Справка по GeoFun" ) );
}



GeoFun::GeoFun()
{
	CtrlLayout ( *this, "GeoFun : Наслаждайтесь Геометрическими Кривыми" );

	Icon ( GFImg::gficon() );

	CtrlLayout ( InputPane );

	Sizeable().Zoomable();

	InputPane.btnImg << [=, this] { SaveToImageFile( "" );};
	InputPane.btnPrint << [=, this] { Print();};
	InputPane.btnShowAnimation << [=, this] { SetAnimation();};

	Add ( dc1.HSizePosZ ( 5, 160 ).VSizePosZ ( 25, 5 ) );
	Add ( InputPane.RightPosZ ( 5, 150 ).VSizePosZ ( 2, 5 ) );
	BackPaint();

	dc1.data.Clear();

	// Check config file available else set default data

	if ( !cfgAvailable )
	{
		InputPane.edArmLength.SetData ( 112 ) ;
		InputPane.edBaseCircleRad.SetData ( 145 );
		InputPane.edPenWidth.SetData ( 3 );
		InputPane.edValP.SetData ( 5 );
		InputPane.edValQ.SetData ( 12 );
		InputPane.edAnimSpeed.SetData ( 5 );

		InputPane.LineColor.SetData ( Blue() ) ;
		InputPane.FillColorM1.SetData ( LtGreen() ) ;
		InputPane.FillColorM2.SetData ( Yellow() ) ;
		InputPane.FillColorB1.SetData ( Blue() ) ;
		InputPane.FillColorB2.SetData ( LtCyan() ) ;

		InputPane.optGradient.Set ( 1 );
		InputPane.optColorFill.Set ( 1 );
		InputPane.CurveType.SetData ( 2 );
	}

	InputPane.CurveType.WhenAction << [=, this] {SetCurveTypeMenu(( int ) InputPane.CurveType.GetData() );};

	InputPane.optGradient.WhenAction << [=, this] {PrepareData();};
	InputPane.optColorFill.WhenAction << [=, this] {PrepareData();};

	InputPane.FillColorB1.WhenAction << [=, this] {PrepareData();};
	InputPane.FillColorB2.WhenAction << [=, this] {PrepareData();};
	InputPane.FillColorM1.WhenAction << [=, this] {PrepareData();};
	InputPane.FillColorM2.WhenAction << [=, this] {PrepareData();};
	InputPane.LineColor.WhenAction << [=, this] {PrepareData();};

	InputPane.edPenWidth.WhenAction << [=, this] {PrepareData();};
	InputPane.edValP.WhenAction << [=, this] {PrepareData();};
	InputPane.edValQ.WhenAction << [=, this] {PrepareData();};
	InputPane.edArmLength.WhenAction << [=, this] {PrepareData();};
	InputPane.edBaseCircleRad.WhenAction << [=, this] {PrepareData();};
	InputPane.edAnimSpeed.WhenAction << [=, this] {SetAnimationSpeed();};
	InputPane.optZoom.WhenAction << [=, this] {PrepareData();};

	// Set timercallback to do first painting with calculations
	// You can not call this directly from constructor
	SetTimeCallback ( 200, [=, this] {FirstDraw();} );

	dc1.ShowTo = 5 ;  // start point of animation.

	menu.Set ([=, this] (Bar& bar) { MainMenu( bar );} );
	dc1.ShowAnimation = false;
}

GUI_APP_MAIN
{
	GeoFun gf ;
	gf.First = true ;
	String cfgfile = ConfigFile();

	if ( FileExists ( cfgfile ) )
	{
		cfgAvailable = true ;

		if ( !LoadFromFile ( gf, cfgfile ) )
		{
			cfgAvailable = false ;
			Exclamation ( "Ошибка при загрузке файла конфигурации!" );
		}
	}

	else
	{
		cfgAvailable = false ;
	}

	gf.Run();

	StoreToFile ( gf );
}
